package com.faithbj.process;

import com.faithbj.common.model.TaskOwner;
import com.faithbj.common.model.User;
import com.faithbj.common.service.UserService;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * User: xiongyuanwei
 * Date: 12-7-28
 * Time: 下午3:57
 */
@Component
public class TaskOwnerSelectorImpl implements TaskOwnerSelector {

    private static Logger log = LoggerFactory.getLogger(TaskOwnerSelectorImpl.class);

    @Autowired
    private UserService userService;

    @Autowired
    private ProcessQueryService processQueryService;

    @Autowired
    private SessionFactory sessionFactory;

    @Override
    @Transactional
    public synchronized String nextUser(long currentTaskId, String regionId) {
        String currentUser = getUser();
        Set<String> tried = Sets.newHashSet();
        TaskModel task = processQueryService.getTaskWithContent(currentTaskId);
        State state = StateFactory.getStateByTaskName(task.getName());
        String nextUser = selectNextUser(regionId, state.isOuter());
        while (!collisionCheck(currentUser, nextUser, task)) {
            tried.add(nextUser);
            nextUser = selectNextUser(regionId, state.isOuter());
            if (tried.contains(nextUser)) {
                log.error("Cyclic reference detected. No more task owner candidates available for task #{}.", currentTaskId);
                throw new IllegalStateException("No more task owner candidates available");
            }
        }
        return nextUser;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public synchronized String selectNextUser(String regionId, boolean isOuter) {
        List<User> candidates = isOuter ? userService.getList("region.id", regionId) : userService.getAll();
        if (candidates == null || candidates.isEmpty()) {
            log.error("NO AVAILABLE CANDIDATES FOR REGION: {} !!!", regionId);
            throw new IllegalArgumentException("No available candidates.");
        }
        Collections.sort(candidates, new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                Date date1 = o1.getCreateDate() == null ? new Date(0) : o1.getCreateDate();
                Date date2 = o2.getCreateDate() == null ? new Date(0) : o2.getCreateDate();
                return date1.compareTo(date2);
            }
        });
        List<String> candidateUsernames = Lists.transform(candidates, new Function<User, String>() {
            @Override
            public String apply(User user) {
                return user.getUsername();
            }
        });
        TaskOwner currentCandidate = getCurrentCandidate();
        String fallbackCandidate = candidates.get(0).getUsername();
        String ret;
        if (currentCandidate == null) {
            ret = fallbackCandidate;
            currentCandidate = new TaskOwner();
        } else {
            int index = candidateUsernames.indexOf(currentCandidate.getUsername());
            if (index == -1) {
                ret = fallbackCandidate;
            } else {
                ret = candidates.get(++index >= candidates.size() ? 0 : index).getUsername();
            }
        }
        currentCandidate.setUsername(ret);
        getSession().saveOrUpdate(currentCandidate);
        return ret;
    }

    private TaskOwner getCurrentCandidate() {
        return (TaskOwner) getSession().get(TaskOwner.class, 0);
    }

    private String getUser() {
        return ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
    }

    private boolean collisionCheck(String currentUser, String nextUser, TaskModel task) {
        State state = StateFactory.getStateByTaskName(task.getName());
        switch (state) {
            case COMPLETION:
            case INTERRUPTED:
                return !currentUser.equals(nextUser);
            case SECOND_CHECK_SELECT:
                String excluded = (String) task.get("excludedForCheck2");
                if (excluded == null) {
                    log.error("Process variable 'excludedForCheck2' is required.");
                    throw new IllegalStateException("Process variable 'excludedForCheck2' is required.");
                }
                return !excluded.equals(nextUser);
            default:
                return true;
        }
    }

    private Session getSession() {
        return sessionFactory.getCurrentSession();
    }
}
